import os
import sys
import time
import shutil
import dataclasses
import subprocess
from pathlib import Path
from multiprocessing import Process,Queue,Value,cpu_count


os.environ["PATH"]+=";D:/Program Files/arm-gnu-toolchain/bin"
os.environ["LANG"]="zh_CN.GBK"

CC="arm-none-eabi-gcc"
AS = CC + ' -x assembler-with-cpp'
OBJCPY="arm-none-eabi-objcopy"
OBJDUMP="arm-none-eabi-objdump"
SIZE="arm-none-eabi-size"

CFLAG=[
  '-mcpu=cortex-m4',
  '-mthumb',
  '-mfpu=fpv4-sp-d16',
  '-mfloat-abi=hard',
  '-O3',
  '-Wall',
  '-fdata-sections',
  '-ffunction-sections',
  # '-u _printf_float', # 使用这个选项会导致调用exit
  '-specs=nano.specs', # 使用nano-newlib
  # debug
  '-g -gdwarf-2'
]

DEF=[
  '-DUSE_STDPERIPH_DRIVER',
  '-DSTM32F429_439xx',
  '-DARM_MATH_CM4',
  '-D__FPU_PRESENT=1',
  # '-D__GNUC__',
  '-D__packed=__attribute__((__packed__))',
  '-D__weak=__attribute__((weak))'
]

INC=[
  '-ISrc/MJPEG',
  '-ISrc/MJPEG/JPEG',
  '-ISrc/STM32/CMSIS/Device/ST/STM32F4xx/Include',
  '-ISrc/STM32/CMSIS/Include',
  '-ISrc/STM32/DSP/Include',
  '-ISrc/STM32/STM32F4xx_StdPeriph_Driver/inc',
  '-ISrc/Drive/Include',
  '-ISrc/FATS',
  '-ISrc/FreeType',
  # '-ISrc/FreeType/devel',
  '-ISrc/FreeType/include',
  '-ISrc/JPEG',
  '-ISrc/lib',
  '-ISrc/lpng1637',
  '-ISrc/lua-5.4.2',
  '-ISrc/lua-5.4.2/src',
  '-ISrc/MP3',
  '-ISrc/MP3/helix',
  '-ISrc/MY',
  '-ISrc/MyApp',
  '-ISrc/MyWin',
  '-ISrc/MyWin/MyWinCore',
  '-ISrc/MyWin/Window',
  '-ISrc/MyWinApp',
  '-ISrc/rt-thread',
  '-ISrc/rt-thread/include',
  # '-ISrc/sqlite3',
  '-ISrc/zlib',
  '-ISrc/NES'
]

SRC_DIR=[
  'Src/MJPEG',
  'Src/STM32/STM32F4xx_StdPeriph_Driver/src',
  'Src/Drive/Source',
  'Src/FreeType/src',
  'Src/JPEG',
  'Src/lpng1637',
  # 'Src/lua-5.4.2/src',
  'Src/MP3',
  'Src/MyApp',
  'Src/MyWin',
  'Src/MyWinApp',
  'Src/rt-thread/src',
  # 'Src/sqlite3',
]

SRC=[
  # 'Src/MY/startup_stm32f429_439xx.s',
  'Src/MY/startup_stm32f429xx.s',
  'Src/STM32/CMSIS/Device/ST/STM32F4xx/Source/Templates/system_stm32f4xx.c',
  "Src/MY/bsp_init.c",
  "Src/MY/main_my.c",
  "Src/MY/stm32f4xx_it.c",
  "Src/MY/sys_api.c",
  "Src/MY/test.c",
  'Src/FATS/diskio.c',
  'Src/FATS/ff.c',
  'Src/FATS/ffsystem.c',
  'Src/FATS/mycc936.c',
  "Src/lib/buff.c",
  'Src/rt-thread/board.c',
  # 'Src/rt-thread/core_delay.c',
  'Src/rt-thread/ports/context_gcc.S',
  'Src/rt-thread/ports/cpuport.c',
  'Src/FreeType/ftdebug.c',
  'Src/FreeType/ftfile.c',

  'Src/zlib/adler32.c',
  'Src/zlib/compress.c',
  'Src/zlib/crc32.c',
  'Src/zlib/deflate.c',
  # 'Src/zlib/gzclose.c',
  # 'Src/zlib/gzlib.c',
  # 'Src/zlib/gzread.c',
  # 'Src/zlib/gzwrite.c',
  'Src/zlib/infback.c',
  'Src/zlib/inffast.c',
  'Src/zlib/inflate.c',
  'Src/zlib/inftrees.c',
  'Src/zlib/trees.c',
  'Src/zlib/uncompr.c',
  'Src/zlib/zutil.c',
  'Src/MP3/helix/arm/asmmisc_gcc.s',
  'Src/MP3/helix/arm/asmpoly_thumb2_gcc.s',
  'Src/MY/syscalls.c',

  'Src/NES/6502_gcc.S',
  'Src/NES/6502cart_gcc.S',
  'Src/NES/nes_apu.c',
  'Src/NES/nes_main.c',
  'Src/NES/nes_ppu.c',
  'Src/NES/nes_mapper.c',
]

LD_FILE="stm32f429ighx_flash.ld"

TARGET="stm32"

OUTPUT="output"


# 找到目录下的所有指定类型的文件
def find_type(path: str, fix: str):
  root = Path(path)
  file_list=[]
  for file in root.rglob(f"*{fix}"):
    if file.is_file():
      file_list.append(f"{file}")
  return file_list


def tran_path(path:str):
  path=os.path.normpath(os.path.join(OUTPUT,path))
  base_path=os.path.dirname(path)
  if not os.path.exists(base_path):
    os.makedirs(base_path)
  return path


# 判断是否需要重新生成
def check_rebuild(dst:str,src:list):
  if(not os.path.exists(dst)):
    return True
  dst_time=os.path.getmtime(dst)
  src_time=[]
  for i in src:
    src_time.append(os.path.getmtime(i))
  src_time.sort()
  if(src_time[-1]>dst_time):
    return True
  return False



# 读取.d文件，返回依赖文件列表
def read_depend_files(name:str):
  with open(name) as f:
    lines=f.readlines()
    t=''
    for line in lines:
      line=line.strip()
      if(line[-1]=='\\'):
        t+=line[:-1]
      else:
        t+=line
    t=t.split(':')[-1].strip()
    t=t.split(' ')
    return t


@dataclasses.dataclass
class cmd_item_t:
  cmd:str
  info:str

def run_cmd(cmd_queue:Queue,cpu_index:int,return_list:Queue,failed_num):
  ack=True
  while(not cmd_queue.empty()):
    if(failed_num.value>0):
      break
    try:
      cmd=cmd_queue.get_nowait()
    except Exception:
      break
    print(f"[{cpu_index}] {cmd.info}")
    # ret=os.system(cmd.cmd)
    try:
      ret = subprocess.run(cmd.cmd, shell=True, timeout=60).returncode
    except subprocess.TimeoutExpired:
      print("命令执行超时！")
      failed_num.value += 1
      ret = -1
    if(ret!=0):
      print(f"[{cpu_index}] ret={ret}")
      ack=False
      failed_num.value+=1
      break
  return_list.put((cpu_index,ack))


def run_cmd_queue(cmd_queue:Queue,cpu_num:int=8):
  if(cmd_queue.empty()):
    return True
  process_list = []
  return_list=Queue()
  failed_num=Value('i',0)
  for i in range(cpu_num):
    p = Process(target=run_cmd,args=(cmd_queue,i,return_list,failed_num,))
    p.daemon=True
    p.start()
    process_list.append(p)
  for i in process_list:
    i.join()
  ret=True
  while not return_list.empty():
    i=return_list.get()
    if(not i[1]):
      ret=False
      print(f"子进程 [{i[0]}] 运行失败")
  return_list.cancel_join_thread()
  # 消耗掉所有数据防止进程无法退出
  while not cmd_queue.empty():
    cmd_queue.get()
  cmd_queue.cancel_join_thread()
  return ret


# 保证目标都存在
def check_exists(src:list):
  for item in src:
    for i in range(10):
      if(os.path.exists(item)):
        break
      time.sleep(0.1)
# 生成依赖关系
def build_depend(src:list):
  CmdQueue=Queue()
  dst_list=[]
  flags=f"{' '.join(INC)} {' '.join(DEF)} {' '.join(CFLAG)}"
  for i in src:
    name_t=os.path.splitext(tran_path(i))
    name=name_t[0]
    file_type=name_t[-1]
    if(not file_type in ['.c','.C','.cpp']):
      continue
    dst='.'.join([name,'d'])
    if(check_rebuild(dst,[i])):
      cmd=f"{CC} -MM {i} -o {dst} {flags}"
      CmdQueue.put(cmd_item_t(cmd,f"更新 {dst}"))
      dst_list.append(dst)
  return run_cmd_queue(CmdQueue)

# 生成中间文件
def build_object(src:list):
  CmdQueue=Queue()
  dst_list=[]
  flags=f"{' '.join(INC)} {' '.join(DEF)} {' '.join(CFLAG)}"
  for i in src:
    name_t=os.path.splitext(tran_path(i))
    name=name_t[0]
    file_type=name_t[-1]
    dst='.'.join([name,'o'])
    cd='.'.join([name,'d'])
    cmd = ''
    if(file_type in ['.c','.C']):
      if(check_rebuild(dst,read_depend_files(cd))):
        cmd=f"{CC} -c {i} -o {dst} {flags}"
    elif(file_type in ['.s','.S','.asm','.ASM']):
      if(check_rebuild(dst,[i])):
        cmd=f"{AS} -c {i} -o {dst} {flags}"
    if(len(cmd)>0):
      CmdQueue.put(cmd_item_t(cmd,f"编译 {dst}"))
      dst_list.append(dst)
  return run_cmd_queue(CmdQueue)


# 生成可执行文件
def build_target(src:list):
  flags=f"{' '.join(INC)} {' '.join(DEF)} {' '.join(CFLAG)}"
  obj_list=[]
  for i in src:
    name=os.path.splitext(tran_path(i))[0]
    obj_list.append('.'.join([name,'o']))
  dst=os.path.join(OUTPUT,TARGET)+".elf"
  if(check_rebuild(dst,obj_list)):
    rsp=f"{' '.join(obj_list)} -o {dst} {flags} \
    -T{LD_FILE} -lc -lm -lnosys -Wl,-Map={OUTPUT}/{TARGET}.map,--cref -Wl,--gc-sections \
    -Wl,-print-memory-usage"
    print(f"链接 {dst}")
    with open(f"{OUTPUT}/{TARGET}.rsp",'w+') as f:
      f.write(rsp.replace('\\','/'))
    ret=os.system(f"{CC} @{OUTPUT}/{TARGET}.rsp")
    return ret==0
  return False



def main():
  global SRC

  if not os.path.exists(OUTPUT):
    os.mkdir(OUTPUT)

  for item in SRC_DIR:
    SRC += find_type(item,'.c')

  if len(sys.argv) > 1:
    l=[]
    if sys.argv[1] == 'show_inc':
      l=INC
    elif sys.argv[1] == 'show_src':
      l=SRC
    l.sort()
    for item in l:
      t=item.replace('\\','/')
      print(f"\"Project/{t}\",")
    return

  if build_depend(SRC):
    if build_object(SRC):
      if build_target(SRC):
        os.system(f"{OBJCPY} -O binary -S {OUTPUT}/{TARGET}.elf {OUTPUT}/{TARGET}.bin")
        os.system(f"{OBJCPY} -O ihex {OUTPUT}/{TARGET}.elf {OUTPUT}/{TARGET}.hex")
        os.system(f"{OBJDUMP} -D {OUTPUT}/{TARGET}.elf > {OUTPUT}/{TARGET}.lst")



if __name__ == "__main__":
  tick_start=time.time()
  main()
  tick_end=time.time()
  print(f"cost: {tick_end-tick_start}")
  # for item in find_type('Src/zlib',".c"):
  #   print(f"'{item}',".replace('\\','/'))
